/**
 *  julive
 *
 *  Create by songli on 2021/05/24
 *  Copyright © 2021 居理买房网. All rights reserved.
 */

package client

import (
	"context"
	"errors"
	"fmt"
	"git.julive.com/julive/user/server/proto/user"
	"git.julive.com/micro/pkg/grpcx"
	"git.julive.com/micro/pkg/logx"
	"git.julive.com/micro/pkg/tracing"
	"github.com/opentracing/opentracing-go"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"io"
)

type User struct {
	service string
	cred    grpcx.GrpcCred
	tracer  opentracing.Tracer
	closer  io.Closer
	logger  *zap.Logger
}

func NewUser(name, version string, opt ...grpcx.Option) (*User, error) {
	cred := grpcx.NewGrpcCred(opt...)
	service := fmt.Sprintf("%s-%s:8081", name, version)
	if version == "latest" {
		service = fmt.Sprintf("%s:8081", name)
	}

	logger := logx.Logger.With(zap.String("module", "user/client"), zap.String("svc", "user"))

	tracer, closer, _ := tracing.InitTracer("julive-user-cli")

	return &User{
		cred:    cred,
		service: service,
		tracer:  tracer,
		closer:  closer,
		logger:  logger,
	}, nil
}

func (tc *User) CreateUser(ctx context.Context, req *user.CreateUserRequest) (*user.CreateUserResponse, error) {
	logger := tc.logger.With(zap.String("func", "CreateUser"))

	rsp, err := tc.invoke(ctx, req, "CreateUser")
	if err != nil {
		logger.Error("User invoke err", zap.Error(err))
	}

	return rsp.(*user.CreateUserResponse), err
}

func (tc *User) GetUserById(ctx context.Context, req *user.GetUserByIdRequest) (*user.GetUserByIdResponse, error) {
	logger := tc.logger.With(zap.String("func", "GetUserById"))

	rsp, err := tc.invoke(ctx, req, "GetUserById")
	if err != nil {
		logger.Error("User invoke err", zap.Error(err))
	}

	return rsp.(*user.GetUserByIdResponse), err
}

func (tc *User) GetUserList(ctx context.Context, req *user.GetUserListRequest) (*user.GetUserListResponse, error) {
	logger := tc.logger.With(zap.String("func", "GetUserList"))

	rsp, err := tc.invoke(ctx, req, "GetUserList")
	if err != nil {
		logger.Error("User invoke err", zap.Error(err))
	}

	return rsp.(*user.GetUserListResponse), err
}

func (tc *User) UpdateUserStatus(ctx context.Context, req *user.UpdateUserStatusRequest) (*user.UpdateUserStatusResponse, error) {
	logger := tc.logger.With(zap.String("func", "UpdateUserStatus"))

	rsp, err := tc.invoke(ctx, req, "UpdateUserStatus")
	if err != nil {
		logger.Error("User invoke err", zap.Error(err))
	}

	return rsp.(*user.UpdateUserStatusResponse), err
}

func (tc *User) UpdateUserCount(ctx context.Context, req *user.UpdateUserCountRequest) (*user.UpdateUserCountResponse, error) {
	logger := tc.logger.With(zap.String("func", "UpdateUserCount"))

	rsp, err := tc.invoke(ctx, req, "UpdateUserCount")
	if err != nil {
		logger.Error("User invoke err", zap.Error(err))
	}

	return rsp.(*user.UpdateUserCountResponse), err
}

func (tc *User) DeleteUserById(ctx context.Context, req *user.DeleteUserByIdRequest) (*user.DeleteUserByIdResponse, error) {
	logger := tc.logger.With(zap.String("func", "DeleteUserById"))

	rsp, err := tc.invoke(ctx, req, "DeleteUserById")
	if err != nil {
		logger.Error("User invoke err", zap.Error(err))
	}

	return rsp.(*user.DeleteUserByIdResponse), err
}

func (tc *User) invoke(ctx context.Context, req interface{}, funcName string) (interface{}, error) {
	logger := tc.logger.With(zap.String("func", funcName))

	conn, err := grpc.Dial(tc.service, grpc.WithTransportCredentials(tc.cred.ClientCred()),
		grpc.WithUnaryInterceptor(tracing.OpenTracingClientInterceptor(tc.tracer)),
		grpc.WithStreamInterceptor(tracing.OpenTracingStreamClientInterceptor(tc.tracer)))

	if err != nil {
		logger.Error("Dial grpc err", zap.Error(err))
		return nil, err
	}
	defer conn.Close()

	userClient := user.NewUserServiceClient(conn)

	var rsp interface{}
	switch r := req.(type) {
	case *user.CreateUserRequest:
		rsp, err = userClient.CreateUser(ctx, r)
	case *user.GetUserByIdRequest:
		rsp, err = userClient.GetUserById(ctx, r)
	case *user.GetUserListRequest:
		rsp, err = userClient.GetUserList(ctx, r)
	case *user.UpdateUserStatusRequest:
		rsp, err = userClient.UpdateUserStatus(ctx, r)
	case *user.UpdateUserCountRequest:
		rsp, err = userClient.UpdateUserCount(ctx, r)
	case *user.DeleteUserByIdRequest:
		rsp, err = userClient.DeleteUserById(ctx, r)
	default:
		err = errors.New("Unsupported request type")
	}
	return rsp, err
}

func (tc *User) GetUserListByStream(ctx context.Context) (user.UserService_GetUserListByStreamClient, error) {
	conn, err := grpc.Dial(tc.service, grpc.WithTransportCredentials(tc.cred.ClientCred()),
		grpc.WithUnaryInterceptor(tracing.OpenTracingClientInterceptor(tc.tracer)),
		grpc.WithStreamInterceptor(tracing.OpenTracingStreamClientInterceptor(tc.tracer)))
	if err != nil {
		return nil, err
	}
	userClient := user.NewUserServiceClient(conn)
	return userClient.GetUserListByStream(ctx)
}

func (tc *User) Close() error {
	return tc.closer.Close()
}
